/*
     Copyright 2007 Joseph M. Ferner

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using log4net;
using OpenServers.Net.Common.Socket;
using OpenServers.Net.FtpServer.State;

namespace OpenServers.Net.FtpServer.Command {
    /// <summary>
    /// base class which contains methods for retreiving data from the client.
    /// </summary>
    public abstract class BaseReceiveDataCommand : BaseCommand {
        private static readonly ILog log = LogManager.GetLogger(typeof(BaseReceiveDataCommand));

        /// <summary>
        /// Begin receiving data from the client.
        /// </summary>
        protected virtual void ReceiveData(IFtpServer server, FtpState state, Stream outputStream) {
            if (state.IsPassive) {
                throw new NotImplementedException("Passive is not implemented");
            } else {
                ISocket socket = state.Listener.CreateSocket();
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                socket.Bind(state.ClientSocket.LocalAddress, 20);
                ReceiveDataState reveiveDataState = new ReceiveDataState(server, state, socket, outputStream);
                log.DebugFormat("receive data begin connect {0}:{1}", state.LastPortCommandIpAddress.ToString(), state.LastPortCommandPort);
                reveiveDataState.Socket.BeginConnect(state.LastPortCommandIpAddress, state.LastPortCommandPort.Value, ConnectCallback, reveiveDataState);
            }
        }

        /// <summary>
        /// Called when the client is connected.
        /// </summary>
        protected virtual void ConnectCallback(IAsyncResult ar) {
            ReceiveDataState state = (ReceiveDataState)ar.AsyncState;
            try {
                log.Debug("receive data connected");
                state.Socket.EndConnect(ar);
                ReceiveMoreData(state);
            } catch (Exception ex) {
                log.Error("", ex);
                state.State.FireError(ex);
            }
        }

        /// <summary>
        /// called when data is received.
        /// </summary>
        protected virtual void ReceiveCallback(IAsyncResult ar) {
            ReceiveDataState state = (ReceiveDataState)ar.AsyncState;
            try {
                SocketError errorCode;
                int count = state.Socket.EndReceive(ar, out errorCode);
                log.DebugFormat("receive data bytes [{0}] error [{1}]", count, errorCode);
                state.FileOutputStream.Write(state.Buffer, 0, count);
                if (count == 0) {
                    state.FileOutputStream.Close();
                    state.Socket.BeginDisconnect(false, DisconnectCallback, state);
                } else {
                    ReceiveMoreData(state);
                }
            } catch (Exception ex) {
                log.Error("", ex);
                state.State.FireError(ex);
            }
        }

        /// <summary>
        /// called when more data should be received.
        /// </summary>
        protected virtual void ReceiveMoreData(ReceiveDataState state) {
            state.Socket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.Partial, ReceiveCallback, state);
        }

        /// <summary>
        /// called when the client is disconnected.
        /// </summary>
        protected virtual void DisconnectCallback(IAsyncResult ar) {
            ReceiveDataState state = (ReceiveDataState)ar.AsyncState;
            try {
                state.Socket.EndDisconnect(ar);
                state.State.Listener.Send(state.State, state.Server.Messages.Get226());
                log.DebugFormat("done receiving data");
            } catch (Exception ex) {
                log.Error("", ex);
                state.State.FireError(ex);
            } finally {
                state.Socket.Close();
            }
        }

        /// <summary>
        /// stores information about receiving data.
        /// </summary>
        protected class ReceiveDataState {
            private readonly FtpState _state;
            private readonly ISocket _socket;
            private readonly Stream _fileOutputStream;
            private readonly byte[] _buffer = new byte[10 * 1024];
            private readonly IFtpServer _server;

            /// <summary>
            /// constructor.
            /// </summary>
            public ReceiveDataState(IFtpServer server, FtpState state, ISocket socket, Stream fileOutputStream) {
                this._server = server;
                this._state = state;
                this._socket = socket;
                this._fileOutputStream = fileOutputStream;
            }

            /// <summary>
            /// the underlying connection state.
            /// </summary>
            public FtpState State {
                get { return _state; }
            }

            /// <summary>
            /// The connected socket.
            /// </summary>
            public ISocket Socket {
                get { return _socket; }
            }

            /// <summary>
            /// The stream to write received data to.
            /// </summary>
            public Stream FileOutputStream {
                get { return _fileOutputStream; }
            }

            /// <summary>
            /// The buffer to put information while receiving data.
            /// </summary>
            public byte[] Buffer {
                get { return _buffer; }
            }

            /// <summary>
            /// The underlying server.
            /// </summary>
            public IFtpServer Server {
                get { return _server; }
            }
        }
    }
}
